//
//  proto_serializer.hpp
// ~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2013-2014 Razvan Coca (razvan dot coca at  gmail dot com)
// 


#ifndef FS_UTILS_HPP
#define FS_UTILS_HPP
#include <string>
#include <map>
#include <cstdint>
#include <climits>
#include <fstream>
#include <boost/system/system_error.hpp>
#include "components/utils/packedmessage.hpp"

template<typename pb_message_t>
boost::shared_ptr<pb_message_t>
load_item(int fd,std::size_t size_limit=USHRT_MAX)
{
    errno=0;
    Proto::PackedMessage<pb_message_t> packed_message;
    Proto::data_buffer buff(Proto::HEADER_SIZE);
    int nread=0;
    nread=read(fd,reinterpret_cast<void*>(&buff[0]),Proto::HEADER_SIZE);
    if(nread<0)
        throw(boost::system::error_code(errno,boost::system::errno_ecat));
    if(nread!=Proto::HEADER_SIZE)
        throw(boost::system::error_code(boost::system::errc::broken_pipe,boost::system::errno_ecat));
    std::size_t len=packed_message.decode_header(buff);
    if(len>size_limit)
        throw std::runtime_error("Invalid message: buffer too large");
    buff.resize(len+Proto::HEADER_SIZE);
    nread=read(fd,reinterpret_cast<void*>(&buff[Proto::HEADER_SIZE]),len);
    if(nread<0)
        throw(boost::system::error_code(errno,boost::system::errno_ecat));
    if(nread!=len)
        throw(boost::system::error_code(boost::system::errc::broken_pipe,boost::system::errno_ecat));
    if(!packed_message.unpack(buff))
        throw std::runtime_error("load_item:Invalid protobuf message read.");
    return packed_message.get_msg();
}

template<typename pb_message_t>
void save_item(int fd, pb_message_t & message)
{
    errno=0;
    Proto::PackedMessage<pb_message_t> packed_message(boost::make_shared<pb_message_t>(message));
    Proto::data_buffer buff;
    if(!packed_message.pack(buff))
        throw std::runtime_error(std::string(__FUNCTION__)+":Error packing pb message");
    int nwritten=write(fd,reinterpret_cast<void*>(&buff[0]),buff.size());
    if(nwritten<0)
        throw(boost::system::error_code(errno,boost::system::errno_ecat));
    if(nwritten!=buff.size())
        throw(boost::system::error_code(boost::system::errc::broken_pipe,boost::system::errno_ecat));
}

template<typename pb_message_t>
boost::shared_ptr<pb_message_t>
load_item(std::ifstream & input,std::size_t size_limit=USHRT_MAX)
{
    errno=0;
    Proto::PackedMessage<pb_message_t> packed_message;
    Proto::data_buffer buff(Proto::HEADER_SIZE);
    input.read(reinterpret_cast<char*>(&buff[0]),Proto::HEADER_SIZE);

    if(input.bad())
        throw(boost::system::error_code(errno,boost::system::errno_ecat));

    std::size_t len=packed_message.decode_header(buff);
    if(len>size_limit) throw std::runtime_error("Invalid message: buffer too large");
    buff.resize(len+Proto::HEADER_SIZE);
    input.read(reinterpret_cast<char*>(&buff[Proto::HEADER_SIZE]),len);

    if(input.bad())
        throw(boost::system::error_code(errno,boost::system::errno_ecat));

    if(!packed_message.unpack(buff))
        throw std::runtime_error("load_item:Invalid protobuf message read.");

    return packed_message.get_msg();
}

template<typename pb_message_t>
void save_item(std::ofstream& output, pb_message_t & message)
{
    errno=0;
    Proto::PackedMessage<pb_message_t> packed_message(boost::make_shared<pb_message_t>(message));
    Proto::data_buffer buff;
    if(!packed_message.pack(buff))
        throw std::runtime_error(std::string(__FUNCTION__)+":Error packing pb message");

    output.write(reinterpret_cast<char*>(&buff[0]),buff.size());

    if(output.fail())
        throw(boost::system::error_code(errno,boost::system::errno_ecat));
}

#endif // FS_UTILS_HPP
